import sys
import cv2
import glob
import os
import os.path
import numpy as np
import sympy as sp
import mpmath as mp
import math

##打气筒　260度　映射　0-100刻度；　０刻度对应水平线条　相对旋转　-65度，逆时针转65度。
##从左边 到右边 5个指针表盘；　　表盘不是水平安放的， 倾斜 offset角度 的基准表盘。
DIALS = [
    #offset=模板上起始刻度放射线相对于Y轴的旋转角，顺时针排列刻度为０刻度偏转角度，逆时针为负数。clockwise=False情况就是最大刻度线的刻度偏转角度！
  #offset==不是水平的摆放－ －偏移角度。,;    clockwise=False 指针标注数字正好反了；
  [-21, True],    ## 45, 表示基准水平线　　往右手边　倾斜了　45度数。
   #上面角度 -65-90 合计是 0刻度的那一条圆心放射线 相对于Y轴（左右两半轴中心线）的旋转角度，逆时针转155度。假定0刻度应该是最顶上那个位点的。
  [0, False],
  [0, True],
  [0, False],
  [0, True]
]


def clear_debug():
    filelist = glob.glob("output/*.jpg")
    for f in filelist:
        os.remove(f)

def write_debug(img, name):
    cv2.imwrite(f"output/{name}.jpg", img)

##从圆盘截图中搜索：最长的直线段，并且尽量是从中心点出发的射线线段。
##查找最佳的 可能线段。　　负反馈：　还需要配合圆心和长度，直线段和原中心点的距离关系。
def searchCenterRayline(edges, source,idx, minLineLength=40):
    cy, cx = source.shape[:2]
    cy=cy/2
    cx=cx/2
    radius=(cx+cy)/2
    ##从最苛刻条件 的开始, 苛刻条件的 检测出的线段 是指针的概率就最大了。
    for threshold in range (120, 30, -3):
        ##threshold逐步降低，越小检测到的线段就越多。　threshold设置的比minLineLength小的话就检测不到了？
        ##maxLineGap太大了许多不是直线段的也能连接变成线段！　　minLineGap:线段上最近两点之间的阈值（交叉覆盖线的宽度）。
        lines = cv2.HoughLinesP(edges, 1, np.pi / 180, threshold=threshold,
                                minLineLength=minLineLength, maxLineGap=6)
        if lines is not None:
            maxlen=0
            whitch= -1
            for i in range(0, len(lines)):
                debug_img = source.copy()
                l = lines[i][0]
                cv2.line(debug_img, (l[0], l[1]), (l[2], l[3]), (0, 255, 0), 2, cv2.LINE_AA)
                write_debug(debug_img, f"findLine-{idx}_{i}")
                p1 = np.array([cx, cy])
                p2 = np.array([l[0], l[1]])
                p3 = p2 - p1
                len1 = math.hypot(p3[0], p3[1])
                p4 = np.array([l[2], l[3]])
                p5 = p4 - p1
                len2 = math.hypot(p5[0], p5[1])
                centerLine=np.minimum(len1, len2)
                ##大约应该从圆形中心附近 发出来的射线。
                if centerLine< radius*0.15:
                    p6 = p4 - p2
                    lenP = math.hypot(p6[0], p6[1])
                    if lenP>maxlen:      ##最长的线条
                        maxlen=lenP
                        whitch=i
            if whitch>=0  and  whitch<len(lines):
                return lines[whitch][0]     ##第一条线段，  多条情况　挑选？
    pass


# turn the edge of a hand into a ray from the point closest to the centre
def generate_hand_ray(center_point, edge):
    center = sp.Point(center_point)
    first = sp.Point(edge[0:2])
    second = sp.Point(edge[2:4])
    first_dist = center.distance(first)
    second_dist = center.distance(second)

    return sp.Ray(first, second) if first_dist < second_dist else sp.Ray(second, first)

#参数offset=模板上起始刻度放射线相对于Y轴的旋转角，顺时针排列刻度为０刻度偏转角度，逆时针为负数。  clockwise=指针标注数字正好反了，True=顺时针;
#def judgeNeedleAngle(config, idx, img, offset=0, clockwise=True):
def judgeNeedleAngle(img, idx, offset=0, clockwise=True):
    #offset, clockwise = config
    offset_r = offset * (np.pi / 180)

    height, width = img.shape[:2]
    print("待识别圆盘图　高度　宽度＝", height, width)
    write_debug(img, f"img-Circles")
    ##指针的旋转中心点，就是圆形截图图片中心
    center = [width / 2, height / 2]
    radius = int(width / 2)
    circle = sp.Circle(sp.Point(center), radius)

    offset_ray = sp.Ray(sp.Point(center), angle=mp.radians(offset))
    offset_img = img.copy()
    origin_point = [ center[0], 0 ]
    offset_point = [
      math.cos(offset_r) * (origin_point[0] - center[0]) - math.sin(offset_r) * (origin_point[1] - center[1]) + center[0],
      math.sin(offset_r) * (origin_point[0] - center[0]) + math.cos(offset_r) * (origin_point[1] - center[1]) + center[1]
    ]
    ##画出 0刻度位置的放射线。
    cv2.line(offset_img, (int(center[0]), int(center[1])), (int(offset_point[0]), int(offset_point[1])), (0, 255, 0), 2)
    write_debug(offset_img, f"Center-{idx}")

    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    ##新增加 begin
    ## 图像二值化   ; Otsu阈值
    _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_TOZERO + cv2.THRESH_OTSU)
    write_debug(binary, f"L-Binary-{idx}")
    ### 中值滤波去噪
    #median = cv2.medianBlur(binary, 1)          # 中值滤波
    #write_debug(median, f"L-medianBlur-{idx}")
    ##新增加 end

    ##滤波； 高斯模糊  ksize(3,3) / (5,5)指定了高斯模糊的半径  ,除去噪声
    blurred = cv2.GaussianBlur(binary, (3, 3), 0)
    ##blurred = cv2.GaussianBlur(gray, (3,3), 0)    #新增加--修改
    write_debug(blurred, f"L-GaussianBlur-{idx}")

    #edges = cv2.Canny(blurred, 30, 130)
    #threshold1=下限阈值，如果像素梯度低于下限阈值，则将像素不被认为边缘，threshold2=上限阈值，如果像素梯度高于上限阈值，则将像素被认为是边缘
    #参数apertureSize为Sobel运算提供内核大小，默认值为3, 这个apertureSize越大！就会显示边缘越多了！
    #参数threshold1越小的，能被检测出的细小细节部分结构部件的边缘就越可能变长了。  threshold2越大被检测出来的部件个数越少。
    edges = cv2.Canny(blurred, 50, 100, apertureSize=3)
    #edges = cv2.Canny(blurred, 50, 200)
    write_debug(edges, f"ptr-Canny-{idx}")

    #[表指针的直线段] 查找最佳的 可能指针线段。
    edge = searchCenterRayline(edges, img ,idx, minLineLength=radius*.28)
    ##参数没调好 就找不到；   edges-4.jpg 基本没剩下什么轮廓了。
    if edge is  None:
        return None   ##没找到指针
    hand_edge_img = img.copy()
    cv2.line(hand_edge_img, (edge[0], edge[1]), (edge[2], edge[3]), (0, 255, 0), 2)
    write_debug(hand_edge_img, f"hand-edge-{idx}")

    hand_ray = generate_hand_ray(center, edge)
    circle_intersection = hand_ray.intersection(circle)[0]

    cv2.line(img, (int(center[0]), int(center[1])), (int(circle_intersection.x), int(circle_intersection.y)), (0, 0, 255), 2)
    write_debug(img, f"result-intersection-{idx}")

    angle_r = math.atan2(circle_intersection.y - center[1], circle_intersection.x - center[0]) - math.atan2(origin_point[1] - center[1], origin_point[0] - center[0])
    angle = angle_r * 180 / np.pi
    #按照　指针线段末端远端点　和圆心点的直线角度，计算刻度数据
    ##表盘不是水平安放的， 倾斜 offset角度 的基准表盘。
    angle =angle-offset

    if angle < 0:
        angle = 360 + angle

    ##映射数字： 360度 　一圈　　 0-10的刻度范围。
    #打气筒　260度　映射　0-100刻度；　
    angle_p = angle/260
    if not clockwise:
        angle_p = 1 - angle_p
    value = 60*angle_p
    ##允许误差范围 +- 5%的角度范围；
    if value >= 0 - 60*0.05  and  value <= 60 *1.05:
        return value        ##最大刻度数100
    pass


clear_debug()

filename = sys.argv[1] if len(sys.argv) > 1 else ""
filename='stepOut/cutout-sourceImg.jpg'
if not os.path.exists(filename):
    print("Usage: python3 power-meter-reader.py <image>")
    exit(1)
##已经按照仪表仪器最小匹配的模板图片自动截取抠出的已经捕获对象小图。
original = cv2.imread(filename)
write_debug(original, "original")

originalSize = original.shape[:2]
#图片尺寸归一化
#resizedSize = (int(originalSize[1] * 0.3), int(originalSize[0] * 0.3))
#resized = cv2.resize(original, resizedSize)
#write_debug(resized, "resized")

gray = cv2.cvtColor(original, cv2.COLOR_BGR2GRAY)

blurred = cv2.GaussianBlur(gray, (5,5), 0)
write_debug(blurred, "blurred")
rows_b, cols_b = blurred.shape
#和图片的尺寸　还有很大关系的！！
#HoughCircles(第四个参数特别重要minDist太小了那么检出圆形太多。第三个参数dp=1:累加器图像的反比分辨；
##按照先前经验　来设置第四个参数minDist=20　；　 param1=100, param2=43, minRadius=40, maxRadius=70
#param1=100是Canny边缘函数的高阈值(Canny下限阈值固定是param1的1/2)；  param1=强边界点{像素累积？}=越小的越会失去宏观只会看见微观的。
#param2参数和Canny无关！！ 越小，检测出圆越多!param_2根据你的图像中的圆大小设置，　param2代表累积能够组成一个圆像素数数量。
#circles = cv2.HoughCircles(blurred, cv2.HOUGH_GRADIENT, 1, 20, np.array([]), param1=100, param2=45, minRadius=int(cols_b*0.38))
circles = cv2.HoughCircles(blurred, cv2.HOUGH_GRADIENT, 1, 20, param1=80,
                           param2=80, minRadius=int(cols_b*0.38), maxRadius=int(cols_b*0.5))

##　找到　多　个　圆形？？
dials = np.uint16(np.around(circles))[0,:]
##按照半径大小排序：　从大到小的半径；
sorted_dials = sorted(dials,reverse=True, key=lambda dial: dial[2])
result = ""
##会找到　多个的　圆形盘？　最佳的是　？　适当的且 是 最大圆盘
for idx, dial in enumerate(sorted_dials):
    x,y,radius = dial
    #圆盘中心点  没有完整的园 ，不合格！
    if x>=radius and y>=radius :
        ##直接截取 圆形的哪一个外包为正方形图片。
        lower_y= y-radius
        lower_x = x-radius
        #从原图截取圆环区域的。
        dial_img = original[lower_y:y+radius,lower_x:x+radius].copy()
        value = judgeNeedleAngle(dial_img, idx, offset=DIALS[0][0], clockwise=DIALS[0][1] )
        ##已经读到了正常的数据就终止。　否则要继续观察下一个　圆形可能的轮廓，不完整的轮廓园也会检测出来。
        if value is not None:
            # draw the outer circle
            cv2.circle(original, (x, y), radius, (0, 255, 0), 2)
            # draw the center of the circle
            cv2.circle(original, (x, y), 2, (0, 0, 255), 3)
            result +=' 中间图序号第'+str(idx)+'个'+ str(value)
            break

if len(result)<=0:
    print("识别失败，没有读到合格的刻度数值", result)
else:
    print("读到的刻度数＝",result)

write_debug(original, "circles")
